import java.util.Random;

import weka.clusterers.ClusterEvaluation;
import weka.clusterers.Clusterer;
import weka.clusterers.EM;
import weka.clusterers.SimpleKMeans;
import weka.core.Instance;
import weka.core.Instances;

import moa.clusterers.clustream.*;

public class Part1Num2 {
	
	public static void Clustream(Instances instances) {
		//instantiate
		Clustream clustream = new Clustream();
		// train on weka instance
		for (int i = 0; i < instances.numInstances(); i++) {
			clustream.trainOnInstanceImpl(instances.instance(i));
		}
		clustream.toString();
		
	}
	
	public static double EuclidianDistance(Instance i1, Instance i2) {
		
		double ans = 0;
		double [] d1 = i1.toDoubleArray();
		double [] d2 = i2.toDoubleArray();
		
		for (int i = 0; i < d1.length; i++) {
			ans += Math.pow( ( d2[i] - d1[i] ) , 2 );
		}
		return Math.sqrt(ans);
	}
	
	public static double FindSmallestNNDistPair(Instances S, int [] ab) {
		
		ab[0] = 0; ab[1] = 1;
		double smallest_distance = -1;
		
		for (int i = 0; i < S.numInstances()-1; i++) {
			for (int j = i+1; j < S.numInstances(); j++) {
				
				double dist = EuclidianDistance(S.instance(i), S.instance(j));
				if ( smallest_distance < 0 || dist < smallest_distance ) {
					ab[0] = i; 
					ab[1] = j; 
					smallest_distance = dist;
				}
			
			}
			
		}
		
		return smallest_distance;
	}
	
	public static Instances KeoughVariableSensitivityAlgorithm(Instances D, int K, double b) {
		
		int k = K+1;
		double [] thresholds = new double[k];
		
		// for 1..k+1, randomly move items from D to S
		
		Random generator = new Random(System.nanoTime());
		int rand = generator.nextInt( D.numInstances() );
		Instances S =  new Instances( D, rand, 1 );
		thresholds[0] = 0;
		D.delete(rand);
		
		for (int i = 1; i < k; i++) {
			rand = generator.nextInt( D.numInstances() );
			S.add( (Instance)D.instance(rand).copy() );
			D.delete(rand);
			thresholds[i] = 0;
		}
		
		// while true
		while (true) {
			
			int [] ab = new int[2];
			double dist = FindSmallestNNDistPair(S, ab);
			S.delete(ab[0]);
			
			// if not all parts of a are in b, then we update b threshold
			if (dist + ab[0] > ab[1]) {
				thresholds[ab[1]] += ( dist + ( (double)ab[0] - (double)ab[1] ) ) * b;
			}
			
			// compact threshholds array
			for (int i = ab[0]; i < k-1; i++) {
				thresholds[i] = thresholds[i+1];
			}
			// reset last threshold to 0 since we are adding another Instance from D
			thresholds[k-1] = 0;
			
			if (D.numInstances() <= 0) {
				return S;
			}
			
			rand = generator.nextInt( D.numInstances() );
			S.add( (Instance)D.instance(rand).copy() );
			D.delete(rand);
			
		}
	}

	public static ClusterEvaluation Eval(final Instances data, final Clusterer c)
			throws Exception {
		final ClusterEvaluation ce = new ClusterEvaluation();
		ce.setClusterer(c);
		ce.evaluateClusterer(data);
		return ce;
	}

	EM em = new EM();

	SimpleKMeans skm = new SimpleKMeans();

	public Part1Num2(final Instances data) throws Exception {
		skm.setDisplayStdDevs(false);
		skm.setDontReplaceMissingValues(false);
		skm.setMaxIterations(500);
		skm.setNumClusters(2);
		skm.setPreserveInstancesOrder(true);
		skm.setSeed(100);
		em.setMaxIterations(10);
		em.setMinStdDev(.00001);
		em.setNumClusters(2);
	}

	public ClusterEvaluation EvalEM(final Instances data) throws Exception {
		return Part1Num2.Eval(data, em);
	}

	public ClusterEvaluation EvalKM(final Instances data) throws Exception {
		return Part1Num2.Eval(data, skm);
	}
}
